

class Bilibala {

  constructor(ele, options) {
    this.options = Object.assign({ }, Bilibala.defaults, options);

    // 清空元素
    this.ele = ele;
    const initText = options.text || ele.innerText || ele.textContent;
    this.ele.innerText = '';

    // 生成光标
    this._createCursor();

    this.queue = []; // 任务队列
    if (initText) {
      const initTask = this._createTask(Bilibala.TYPE, {
        text: initText,
      });
      this.queue.push(initTask);
    }
    this.currentTask = null;
    this.status = Bilibala.STATUS.READY;
  }
  _createCursor() {
    this.cursor = document.createElement('div');
    this.cursor.className = this.options.cursorClass;
    this.ele.appendChild(this.cursor);
  }

  _createTask(Task, options) {
    return new Task(options);
  }

  run() {
    if (this.queue.length > 0) {
      this.currentTask = this.queue.shift();
      this.setStatus(Bilibala.STATUS.RUNNING);
      this.currentTask.run(this);
    } else {
      this.stop();
    }
    return this;
  }

  setStatus(status) {
    this.ele.removeAttribute(Bilibala.STATUS[this.status]);
    this.status = status;
    this.ele.setAttribute(Bilibala.STATUS[status], '');
    const s = Bilibala.STATUS;
    const active = this.options.cursorActiveClass;
    if (this.status === s.RUNNING) {
      this.cursor.classList.remove(active);
    } else {
      this.cursor.classList.add(active);
    }
  }

  setSpeed(speed) {
    if (typeof speed === 'number' && speed > 0) {
      if (this.currentTask) {
        this.currentTask.setSpeed(speed);
      } else if (typeof speed ){
        this.options.speed = speed;
      }
    } else {
      throw TypeError('speed must be a number bigger than 0');
    }
  }

  // 暂停
  stop() {
    this.currentTask.stop();
    this.setStatus(Bilibala.STATUS.STOP);
    return this;
  }

  type(ch) {
    let tmpEle = document.createElement('span');
    tmpEle.className = this.options.charClass;
    tmpEle.innerText = ch;
    this.ele.insertBefore(tmpEle, this.cursor);
    tmpEle = null;
  }

  backspace() {
    let prevEle = this.cursor.previousElementSibling;
    if (prevEle === null) return false;
    prevEle.remove();
    prevEle = null;
    return true;
  }

  moveCursor() {
    let preEle = this.cursor.previousElementSibling;
    if (preEle === null) return false;
    this.ele.insertBefore(this.cursor, preEle);
    preEle = null;
    return true;
  }

  action(type, options) {
    // 写模式
    if (typeof type === 'string') {
      this.queue.push(this._createTask(Bilibala.TYPE, Object.assign({ text: type }, options)));
    } else if (typeof type === 'function') {
      this.queue.push(this._createTask(type, options));
    }
    return this;
  }

  cool(bilistr) {

    return this;
  }

}

Bilibala.defaults = {
  /* 每分钟输入多少字 */
  speed: 1000,
  /* 光标样式类名 */
  cursorClass: 'cursor',
  cursorActiveClass: 'stay',
  /* 每个字符的样式类名 */
  charClass: '',
  /* 要输出的文字, 将会覆盖元素中的文本 */
  text: '',
};

Bilibala.STATUS = {};
['READY', 'RUNNING', 'STOP'].forEach((item, index) => {
  Bilibala.STATUS[item] = index;
  Bilibala.STATUS[index] = item;
});




class Task {

  constructor(options) {
    this.options = Object.assign({ }, Task.defaults, options);
    this.speed = this.options.speed;
  }

  setSpeed(speed) {
    this.speed = speed;
  }

  stop() {
    clearInterval(this.counter);
  }

  destroy() {
    this.stop();
    this.options = null;
  }

  next(controller) {
    this.destroy();
    controller.run();
  }

  run() {

  }

}

Task.defaults = {
  speed: Bilibala.defaults.speed,
};

class BackspaceTask extends Task {
  constructor(options) {
    if (typeof options.wordCount !== 'number') {
      throw TypeError('wordCount arg must be number.');
    }
    super(options);
    this.count = 0;
  }
  
  destroy () {
    super.destroy();
    this.count = 0;
  }

  run(controller) {
    this.counter = setInterval(() => {
      if (this.count < this.options.wordCount) {
        if (!controller.backspace()) this.next(controller);
        this.count += 1;
      } else {
        this.next(controller);
      }
    }, 60000 / this.speed);
  }

}


class MoveCursorTask extends Task {
  constructor(options) {
    if (typeof options.wordCount !== 'number') {
      throw TypeError('wordCount arg must be number');
    }
    super(options);
    this.pos = 0;
  }

  destroy () {
    super.destroy();
    this.pos = 0;
  }

  run(controller) {
    this.counter = setInterval(() => {
      if (this.pos < this.options.wordCount) {
        if (!controller.moveCursor()) this.next(controller);
        this.pos += 1;
      } else {
        this.next(controller);
      }
    }, 60000 / this.speed);
  }
}

class TypeTask extends Task {
  constructor(options) {
    if (typeof options.text !== 'string') {
      throw TypeError('text arg must be string.');
    }
    super(options);
    this.index = 0;
  }

  destroy () {
    super.destroy();
    this.index = 0;
  }

  run(controller) {
    this.counter = setInterval(() => {
      const currentChar = this.options.text[this.index];
      if (currentChar) {
        controller.type(currentChar);
        this.index += 1;
      } else {
        this.next(controller);
      }
    }, 60000 / this.speed);
  }
}


class StayTask extends Task {
  constructor(options) {
    if (typeof options.interval !== 'number') {
      throw TypeError('interval must be number.');
    }
    super(options);
  }

  run(controller) {
    controller.setStatus(Bilibala.STATUS.STOP);
    this.counter = setInterval(() => {
      this.next(controller);
    }, this.options.interval);
  }
}


Bilibala.TYPE = TypeTask;
Bilibala.BACKSPACE = BackspaceTask;
Bilibala.MOVE_CURSOR = MoveCursorTask;
Bilibala.STAY = StayTask;




window.onload = function() {
  const eles = [...document.querySelectorAll('p')];
  eles.forEach((ele) => {
    const typer = new Bilibala(ele, { speed: 800 });
    typer.cool('[*900]hello, [-world!]human![.2000] I am[1hello>2] a robot![<1][2]')
    .action(Bilibala.BACKSPACE, { wordCount: 7 })
    .action('hello\nI am jeff!', { speed: 900 })
    .action(Bilibala.STAY, { interval: 2000 })
    .action(Bilibala.BACKSPACE, { wordCount: 10 })
    .action('hello world!')
    .run();

  });
};